// package handler：文档相关HTTP请求处理器，提供文档的CRUD操作接口
// 与路由层解耦，专注于请求参数验证、业务逻辑处理、数据库交互和响应返回，同时提供Swagger接口文档注释
package handler

import (
	"github.com/gin-gonic/gin" // Gin Web框架：用于处理HTTP请求、参数绑定、响应返回等
	"go-blog/model"
	"gorm.io/gorm" // GORM：Go语言ORM框架，用于数据库交互（查询、创建、更新、删除）
	"net/http"     // 标准库HTTP包：提供HTTP状态码常量
	"strconv"      // 标准库字符串处理包：用于字符串与整数类型转换（如文档ID）
	"time"         // 标准库时间包：用于设置文档更新时间
)

// @title 博客系统-文档管理API
// @version 1.0
// @description 该模块提供博客文档的创建、查询、更新、删除等核心操作接口
// @termsOfService http://example.com/terms/

// @contact.name API Support
// @contact.url http://example.com/support
// @contact.email support@example.com

// @license.name Apache 2.0
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html

// @host localhost:8081
// @BasePath /api
// @schemes http https

// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description

// DocRequest 创建/更新文档的请求参数结构体
// @Schema DocRequest
// @description 创建或更新文档时的请求参数，包含标题、内容、分类三个必填字段
type DocRequest struct {
	Title    string `json:"title" binding:"required,min=1,max=100"` // @json title 文档标题，必填，长度1-100字符
	Content  string `json:"content" binding:"required,min=10"`      // @json content 文档内容，必填，至少10字符（确保内容不为空或过短）
	Category string `json:"category" binding:"required"`            // @json category 文档分类，必填，仅支持tech（技术）、life（生活）、other（其他）
}

// GetDocs 获取文档列表接口
// @Summary 获取文档列表
// @Description 支持按分类筛选文档，默认按创建时间倒序返回所有文档（无需登录）
// @Tags 文档管理
// @Accept json
// @Produce json
// @Param category query string false "文档分类筛选条件，可选值：tech/life/other"
// @Success 200 {object} Response{data=[]model.Doc} "查询成功，返回文档列表"
// @Failure 500 {object} Response "查询失败，返回错误信息"
// @Router /docs [get]
func GetDocs(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 从URL查询参数中获取分类筛选条件（如：/docs?category=tech）
		// 若前端未传category参数，则category为空字符串，不进行分类筛选
		category := c.Query("category")

		// 2. 构建数据库查询会话
		// 默认按创建时间（created_at）倒序排列，确保最新文档排在前面
		session := db.Order("created_at DESC")

		// 3. 若分类参数不为空且合法（通过model层的IsValidCategory校验），添加分类筛选条件
		// 避免查询非法分类导致的无效结果，提前拦截不合法请求
		if category != "" && model.IsValidCategory(category) {
			session = session.Where("category = ?", category) // WHERE category = 'xxx'
		}

		// 4. 执行查询，将结果存入docs切片（[]model.Doc）
		var docs []model.Doc
		if err := session.Find(&docs).Error; err != nil {
			// 若查询失败（如数据库连接异常），返回500内部服务器错误
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,                   // 错误码：500表示服务器端错误
				"msg":  "查询失败：" + err.Error(), // 错误信息：携带具体错误详情便于调试
			})
			return // 终止函数，避免后续无效执行
		}

		// 5. 查询成功，返回200 OK响应，携带文档列表数据
		c.JSON(http.StatusOK, gin.H{
			"code": 0,    // 成功码：0表示业务处理成功
			"data": docs, // 响应数据：文档列表（[]model.Doc）
		})
	}
}

// CreateDoc 创建文档接口
// @Summary 创建文档
// @Security BearerAuth
// @Description 登录用户创建文档，需验证分类合法性，文档作者默认为当前登录用户
// @Tags 文档管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body DocRequest true "创建文档的请求参数"
// @Success 200 {object} Response{data=model.Doc} "创建成功，返回创建的文档完整信息"
// @Failure 400 {object} Response "参数错误（如缺少字段、分类无效）"
// @Failure 401 {object} Response "未登录（缺少或无效的JWT令牌）"
// @Failure 500 {object} Response "创建失败（如数据库错误）"
// @Router /docs [post]
func CreateDoc(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 绑定并验证请求参数：将前端JSON参数解析到DocRequest结构体
		// c.ShouldBindJSON()会自动触发结构体tag中的binding规则校验（如必填、长度）
		var req DocRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			// 若参数绑定失败（如缺少必填字段、长度不满足），返回400Bad Request
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "参数错误：" + err.Error(), // 提示具体参数错误（如"Title为必填字段"）
			})
			return
		}

		// 2. 验证分类合法性：调用model层的IsValidCategory方法，确保分类是允许的值（如tech/life/other）
		if !model.IsValidCategory(req.Category) {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "无效分类：仅支持tech/life/other", // 明确告知合法分类选项
			})
			return
		}

		// 3. 获取当前登录用户ID："user_id"由登录中间件（如JWT）在请求处理前注入到上下文
		// 此处假设中间件已确保"user_id"存在且类型为uint，直接类型断言
		userID, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "未登录：请先登录再创建文档",
			})
			return
		}

		// 4. 构建Doc数据模型：将请求参数与用户ID组装为数据库存储的model.Doc实例
		doc := model.Doc{
			Title:    req.Title,     // 文档标题（来自请求）
			Content:  req.Content,   // 文档内容（来自请求）
			Category: req.Category,  // 文档分类（来自请求，已验证合法）
			AuthorID: userID.(uint), // 作者ID（来自登录用户，关联用户表）
			// CreatedAt/UpdatedAt由GORM自动填充（需在model.Doc中配置autoCreateTime/autoUpdateTime）
		}

		// 5. 执行数据库创建操作：将doc实例插入到docs表
		if err := db.Create(&doc).Error; err != nil {
			// 若创建失败（如数据库错误），返回500内部服务器错误
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,
				"msg":  "创建失败：" + err.Error(),
			})
			return
		}

		// 6. 创建成功，返回200 OK响应，携带创建后的文档完整信息（含自动生成的ID、时间等）
		c.JSON(http.StatusOK, gin.H{
			"code": 0,
			"msg":  "创建成功",
			"data": doc,
		})
	}
}

// UpdateDoc 更新文档接口
// @Summary 更新文档
// @Security BearerAuth
// @Description 仅文档作者可更新文档，需验证文档ID有效性、分类合法性及权限
// @Tags 文档管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "文档ID" example(1)
// @Param request body DocRequest true "更新文档的请求参数"
// @Success 200 {object} Response{data=model.Doc} "更新成功，返回更新后的文档信息"
// @Failure 400 {object} Response "参数错误（如ID无效、缺少字段、分类无效）"
// @Failure 401 {object} Response "未登录（缺少或无效的JWT令牌）"
// @Failure 403 {object} Response "无权限（非文档作者）"
// @Failure 404 {object} Response "文档不存在"
// @Failure 500 {object} Response "更新失败（如数据库错误）"
// @Router /docs/{id} [put]
func UpdateDoc(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 解析路径参数中的文档ID：从URL中获取"id"（如/cdocs/:id），转换为整数
		docIDStr := c.Param("id")
		docID, err := strconv.Atoi(docIDStr)
		// 校验ID合法性：转换失败（如非数字）或ID小于等于0，均为无效ID
		if err != nil || docID <= 0 {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "文档ID无效",
			})
			return
		}

		// 2. 绑定并验证更新参数：与创建文档共用DocRequest结构体，复用参数校验逻辑
		var req DocRequest
		if err := c.ShouldBindJSON(&req); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "参数错误：" + err.Error(),
			})
			return
		}

		// 3. 验证分类合法性：更新分类时需确保新分类是合法的
		if !model.IsValidCategory(req.Category) {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "无效分类：仅支持tech/life/other",
			})
			return
		}

		// 4. 查询待更新的文档：通过ID查询文档，同时验证文档是否存在
		var doc model.Doc
		if err := db.First(&doc, docID).Error; err != nil {
			// 分支1：文档不存在（gorm.ErrRecordNotFound是GORM定义的"记录未找到"错误）
			if err == gorm.ErrRecordNotFound {
				c.JSON(http.StatusOK, gin.H{ // 此处返回200+code=1，避免暴露404（按业务约定）
					"code": 1,
					"msg":  "文档不存在",
				})
				return
			}
			// 分支2：查询过程出错（如数据库异常）
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,
				"msg":  "查询失败：" + err.Error(),
			})
			return
		}

		// 5. 权限校验：仅文档作者可修改（当前登录用户ID == 文档的AuthorID）
		currentUserID, exists := c.Get("user_id") // 从上下文获取登录用户ID
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "未登录：请先登录再更新文档",
			})
			return
		}
		// 类型断言：将currentUserID转换为uint（与AuthorID类型一致）
		if doc.AuthorID != currentUserID.(uint) {
			// 无权限，返回403 Forbidden
			c.JSON(http.StatusForbidden, gin.H{
				"code": 403,
				"msg":  "无权限：仅作者可修改",
			})
			return
		}

		// 6. 更新文档字段：覆盖标题、内容、分类，手动更新UpdatedAt时间（或依赖GORM自动填充）
		doc.Title = req.Title
		doc.Content = req.Content
		doc.Category = req.Category
		doc.UpdatedAt = time.Now() // 手动设置更新时间，确保时间准确性

		// 7. 执行数据库更新操作：保存修改后的doc实例到数据库
		if err := db.Save(&doc).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,
				"msg":  "更新失败：" + err.Error(),
			})
			return
		}

		// 8. 更新成功，返回200 OK响应，携带更新后的文档信息
		c.JSON(http.StatusOK, gin.H{
			"code": 0,
			"msg":  "更新成功",
			"data": doc,
		})
	}
}

// DeleteDoc 删除文档接口
// @Summary 删除文档
// @Security BearerAuth
// @Description 仅文档作者可软删除文档，删除后数据不会物理删除（仅标记DeletedAt）
// @Tags 文档管理
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param id path int true "文档ID" example(1)
// @Success 200 {object} Response "删除成功，返回提示信息"
// @Failure 400 {object} Response "参数错误（如文档ID无效）"
// @Failure 401 {object} Response "未登录（缺少或无效的JWT令牌）"
// @Failure 403 {object} Response "无权限（非文档作者）"
// @Failure 404 {object} Response "文档不存在"
// @Failure 500 {object} Response "删除失败（如数据库错误）"
// @Router /docs/{id} [delete]
func DeleteDoc(db *gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 解析并验证路径参数中的文档ID
		docIDStr := c.Param("id")
		docID, err := strconv.Atoi(docIDStr)
		if err != nil || docID <= 0 {
			c.JSON(http.StatusBadRequest, gin.H{
				"code": 400,
				"msg":  "文档ID无效",
			})
			return
		}

		// 2. 查询待删除的文档：验证文档存在性，同时获取文档的AuthorID用于权限校验
		var doc model.Doc
		if err := db.First(&doc, docID).Error; err != nil {
			// 分支1：文档不存在
			if err == gorm.ErrRecordNotFound {
				c.JSON(http.StatusOK, gin.H{
					"code": 1,
					"msg":  "文档不存在",
				})
				return
			}
			// 分支2：查询出错
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,
				"msg":  "查询失败：" + err.Error(),
			})
			return
		}

		// 3. 权限校验：仅文档作者可删除
		currentUserID, exists := c.Get("user_id")
		if !exists {
			c.JSON(http.StatusUnauthorized, gin.H{
				"code": 401,
				"msg":  "未登录：请先登录再删除文档",
			})
			return
		}
		if doc.AuthorID != currentUserID.(uint) {
			c.JSON(http.StatusForbidden, gin.H{
				"code": 403,
				"msg":  "无权限：仅作者可删除",
			})
			return
		}

		// 4. 执行软删除：GORM的Delete方法默认是软删除（若model.Doc有gorm.Model嵌入，含DeletedAt字段）
		// 软删除仅更新DeletedAt字段，不会物理删除数据，便于数据恢复和日志追溯
		if err := db.Delete(&doc).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{
				"code": 500,
				"msg":  "删除失败：" + err.Error(),
			})
			return
		}

		// 5. 删除成功，返回200 OK响应
		c.JSON(http.StatusOK, gin.H{
			"code": 0,
			"msg":  "删除成功",
		})
	}
}

// Response 通用API响应结构体（用于Swagger文档识别统一响应格式）
// @Schema Response
type Response struct {
	Code int         `json:"code"` // 业务状态码：0成功，非0失败
	Msg  string      `json:"msg"`  // 提示信息：成功/失败描述
	Data interface{} `json:"data"` // 响应数据：成功时返回的业务数据（可选）
}
